/*******************************************************************************
 * Copyright (c) 2010-2011 Robert Fraser                                       *
 *                                                                             *
 * Permission is hereby granted, free of charge, to any person obtaining a     *
 * copy of this software and associated documentation files (the "Software"),  *
 * to deal in the Software without restriction, including without limitation   *
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,    *
 * and/or sell copies of the Software, and to permit persons to whom the       *
 * Software is furnished to do so, subject to the following conditions:        *
 *                                                                             *
 *   - The above copyright notice and this permission notice shall be included *
 *     in all copies or substantial portions of the Software.                  *
 *                                                                             *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  *
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,    *
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE *
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER      *
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING     *
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER         *
 * DEALINGS IN THE SOFTWARE.                                                   *
 *******************************************************************************/

using System.Windows.Media;
using zeal.core;
using zeal.ui.util;

namespace zeal.ui.graph
{
    internal sealed class EdgeColorizer
    {
        public const double DARK_LIT_FACTOR = 0.75;
        public const double DARK_SAT_FACTOR = 0.75;
        public const double PEN_THICKNESS = 2.5;

        public readonly IValueConverter<TextureType, bool, bool, bool, bool, bool, GraphPort.ConnectionStates, bool, Pen> portBorderConverter;
        public readonly IValueConverter<TextureType, bool, bool, bool, bool, bool, GraphPort.ConnectionStates, bool, Brush> portFillConverter;
        public readonly IValueConverter<TextureType, bool, bool, Pen> edgeConverter;
        private readonly TypeColorScheme[] _colors;

        private EdgeColorizer(TypeColorScheme[] colors)
        {
            _colors = colors;
            portBorderConverter = ValueConverter.get<TextureType, bool, bool, bool, bool, bool, GraphPort.ConnectionStates, bool, Pen>(getPortBorder);
            portFillConverter = ValueConverter.get<TextureType, bool, bool, bool, bool, bool, GraphPort.ConnectionStates, bool, Brush>(getPortFill);
            edgeConverter = ValueConverter.get<TextureType, bool, bool, Pen>(getEdge);
        }

        public Pen getDefaultPen(TextureType type)
        {
            return getColorScheme(type).getPen(type.dataType, true);
        }

        public Pen getPortBorder(TextureType type, bool isInput, bool isConnected, 
            bool isNodeSelected, bool isEdgeSelected, bool isHovered, GraphPort.ConnectionStates dragState, bool isDeletionTarget)
        {
            return getColorScheme(type).getPen(type.dataType, isNodeSelected || isEdgeSelected  || isHovered ||
                dragState == GraphPort.ConnectionStates.SOURCE || dragState == GraphPort.ConnectionStates.VALID);
        }

        public Brush getPortFill(TextureType type, bool isInput, bool isConnected,
            bool isNodeSelected, bool isEdgeSelected, bool isHovered, GraphPort.ConnectionStates dragState, bool isDeletionTarget)
        {
            TypeColorScheme scheme = getColorScheme(type);

            // If it's the source of the connection action, then it's already partially connected. Return active.
            if (dragState == GraphPort.ConnectionStates.SOURCE) return scheme.activeBrush;

            // If it's either a potential source or target but is not currently connected, return the hover. If it _is_
            // connected but not a deletion target, it's an output that's already connected to other things, so just
            // return active.
            if (isHovered || dragState == GraphPort.ConnectionStates.VALID) return isConnected ? scheme.activeBrush : scheme.portHoverBrush;
                
            // Okay, if we reach this point, the mouse is nowhere near it... so just return the regular fill
            return !isConnected ? Brushes.Black : scheme.getBrush(isNodeSelected || isEdgeSelected);
        }

        public Pen getEdge(TextureType type, bool isSelected, bool isDeletionTarget)
        {
            // TODO: a black deletion taget brush looks crap.... figure out what to do when the port is a deletion target
            return getColorScheme(type).getPen(type.dataType, isSelected);
        }

        private TypeColorScheme getColorScheme(TextureType type) { return _colors[(int) type.pixelType]; }
        private sealed class TypeColorScheme
        {
            public readonly Brush activeBrush;
            public readonly Brush inactiveBrush;
            public readonly Brush portHoverBrush;
            private readonly Pen[] _activePens;
            private readonly Pen[] _inactivePens;

            public Brush getBrush(bool active) { return active ? activeBrush : inactiveBrush; }
            public Pen getPen(TextureDataType dataType, bool active) { return (active ? _activePens : _inactivePens)[(int) dataType]; }

            public TypeColorScheme(Color normal) : this(normal, normal.multiplyHsl(DARK_LIT_FACTOR, DARK_SAT_FACTOR)) { }
            public TypeColorScheme(Color normal, Color dark)
            {
                activeBrush = createSolidBrush(normal);
                inactiveBrush = createSolidBrush(dark);
                portHoverBrush = createRadialBrush(normal, Colors.Black);
                _activePens = createPens(activeBrush);
                _inactivePens = createPens(inactiveBrush);
            } 

            private static Brush createSolidBrush(Color c) { return new SolidColorBrush(c).frozen(); }
            private static Brush createRadialBrush(Color inner, Color outer) { return new RadialGradientBrush(inner, outer).frozen(); }
            private static Pen createPen(Brush brush) { return new Pen(brush, PEN_THICKNESS).frozen(); }

            private static Pen[] createPens(Brush brush)
            {
                Pen[] pens = new Pen[(int) TextureDataType.NUM_DATA_TYPES];
                pens[(int) TextureDataType.PIXEL] = createPen(brush);
                pens[(int) TextureDataType.TEXTURE2D] = createPen(brush);
                return pens;
            }
        }

        // Singleton management
        public static readonly EdgeColorizer instance;
        static EdgeColorizer()
        {
            TypeColorScheme[] schemes = new TypeColorScheme[(int) TexturePixelType.NUM_PIXEL_TYPES];
            schemes[(int) TexturePixelType.VARIABLE] = new TypeColorScheme(Colors.White, Color.FromRgb(192,192,192));
            schemes[(int)TexturePixelType.ARGB] = new TypeColorScheme(Colors.Red);
            schemes[(int)TexturePixelType.FLOAT] = new TypeColorScheme(Colors.Blue);
            schemes[(int)TexturePixelType.VECTOR2] = new TypeColorScheme(Colors.Lime);
            instance = new EdgeColorizer(schemes);
        }
    }
}